from typing import Dict, List, Optional, Tuple, Callable
import os
import re
import requests
from abc import ABC, abstractmethod

class VideoFormat:
    """视频格式信息类"""
    def __init__(
        self,
        format_id: str,
        height: int,
        width: int,
        ext: str = "mp4",
        format_note: str = "",
        url: str = "",
        filesize: int = 0,
        tbr: float = 0,
    ):
        self.format_id = format_id
        self.height = height
        self.width = width
        self.ext = ext
        self.format_note = format_note
        self.url = url
        self.filesize = filesize
        self.tbr = tbr

class VideoInfo:
    """视频信息类"""
    def __init__(
        self,
        title: str,
        formats: List[VideoFormat],
        cover_url: str
    ):
        self.title = title
        self.formats = formats
        self.cover_url = cover_url

class BaseDownloader(ABC):
    """下载器基类"""
    def __init__(self):
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
        }

    @abstractmethod
    async def extract_video_info(self, url: str) -> VideoInfo:
        """提取视频信息"""
        pass

    @abstractmethod
    async def download_video(
        self,
        url: str,
        format_id: str,
        progress_callback: Optional[Callable[[float], bool]] = None
    ) -> Tuple[bool, str]:
        """下载视频"""
        pass

    def _clean_filename(self, filename: str) -> str:
        """清理文件名中的非法字符"""
        return re.sub(r'[\\/:*?"<>|]', '_', filename)

    def _get_save_path(self, title: str, ext: str) -> str:
        """获取保存路径"""
        safe_title = self._clean_filename(title)
        return os.path.join(os.path.expanduser("~"), "Downloads", "VideoDownloader", f"{safe_title}.{ext}")

class DouyinDownloader(BaseDownloader):
    """抖音视频下载器"""
    def __init__(self):
        super().__init__()
        self.cookies: Dict[str, str] = {}
        self.headers['Referer'] = 'https://www.douyin.com'

    def set_cookies_file(self, file_path: str) -> None:
        """设置 cookies 文件路径并解析内容"""
        self.cookies_file = file_path
        self.load_cookies()

    def load_cookies(self) -> None:
        """加载 cookies 文件内容"""
        try:
            with open(self.cookies_file, "r", encoding="utf-8") as f:
                cookies_content = f.read().strip()
                if cookies_content.startswith('#'):
                    cookies_content = '\n'.join(line for line in cookies_content.split('\n') if not line.startswith('#'))
                
                for cookie in cookies_content.split(';'):
                    cookie = cookie.strip()
                    if '=' in cookie:
                        name, value = cookie.split('=', 1)
                        name = name.strip()
                        value = value.strip()
                        if name and value:
                            self.cookies[name] = value
        except Exception as e:
            raise Exception(f"无法加载 Cookies 文件: {str(e)}")

    async def extract_video_info(self, url: str) -> VideoInfo:
        """提取抖音视频信息"""
        try:
            response = requests.get(url, headers=self.headers, cookies=self.cookies)
            response.raise_for_status()
            
            # 提取视频ID
            video_id_match = re.search(r'video/(\d+)', url)
            if not video_id_match:
                raise Exception("无法提取视频ID")
            
            video_id = video_id_match.group(1)
            api_url = f"https://www.douyin.com/aweme/v1/web/aweme/detail/?aweme_id={video_id}"
            api_response = requests.get(api_url, headers=self.headers, cookies=self.cookies)
            api_response.raise_for_status()
            
            video_data = api_response.json()
            video_detail = video_data.get("aweme_detail", {})
            video = video_detail.get("video", {})
            
            # 提取标题
            title = video_detail.get("desc", "抖音视频")
            
            # 提取视频格式
            formats = []
            play_addr = video.get("play_addr", {})
            url_list = play_addr.get("url_list", [])
            if url_list:
                formats.extend([
                    VideoFormat(
                        format_id="hd",
                        height=720,
                        width=1280,
                        format_note="高清",
                        url=url_list[0]
                    ),
                    VideoFormat(
                        format_id="sd",
                        height=480,
                        width=854,
                        format_note="标清",
                        url=url_list[0]
                    )
                ])
            
            # 提取封面
            cover_url = video.get("cover", {}).get("url_list", [""])[0]
            
            return VideoInfo(title=title, formats=formats, cover_url=cover_url)
            
        except requests.exceptions.RequestException as e:
            raise Exception(f"网络请求失败: {str(e)}")
        except Exception as e:
            raise Exception(f"解析视频信息失败: {str(e)}")

    async def download_video(
        self,
        url: str,
        format_id: str,
        progress_callback: Optional[Callable[[float], bool]] = None
    ) -> Tuple[bool, str]:
        """下载视频"""
        try:
            video_info = await self.extract_video_info(url)
            
            # 获取下载地址
            selected_format = next(
                (fmt for fmt in video_info.formats if fmt.format_id == format_id),
                None
            )
            if not selected_format:
                return False, "无法获取下载地址"
            
            # 下载视频
            response = requests.get(selected_format.url, headers=self.headers, cookies=self.cookies, stream=True)
            response.raise_for_status()
            
            total_size = int(response.headers.get('content-length', 0))
            block_size = 1024
            downloaded = 0
            
            save_path = self._get_save_path(video_info.title, selected_format.ext)
            
            with open(save_path, 'wb') as f:
                for data in response.iter_content(block_size):
                    downloaded += len(data)
                    f.write(data)
                    if progress_callback and total_size:
                        progress = downloaded / total_size
                        if not progress_callback(progress):
                            return False, "下载已取消"
            
            return True, save_path
            
        except Exception as e:
            return False, f"下载失败: {str(e)}"

class DownloaderFactory:
    """下载器工厂类"""
    @staticmethod
    def get_downloader(url: str) -> Optional[BaseDownloader]:
        if "douyin.com" in url or "iesdouyin.com" in url:
            return DouyinDownloader()
        # 其他下载器的实现...
        return None 